﻿using System;
using System.Collections.Generic;
using System.Data.Entity;
using System.Data.Entity.Infrastructure;
using System.Linq;
using System.Linq.Expressions;
using System.Text;
using System.Threading.Tasks;
using JY.Common.EntityBase;
using JY.Common.Repository;

namespace JY.Repository.EntityFramework
{
    /// <summary>
    /// 工作单元
    /// </summary>
    public class UnitOfWorkOfQuery : IUnitOfWorkOfQuery
    {
        #region # 变量
        /// <summary>
        /// EF（读）上下文对象字段
        /// </summary>
        protected readonly DbContext QueryDbContext;
        /// <summary>
        /// 同步锁
        /// </summary>
        private static readonly object Sync;
        #endregion

        #region # 构造器
        /// <summary>
        /// 静态构造器
        /// </summary>
        static UnitOfWorkOfQuery()
        {
            Sync = new object();
        }
        /// <summary>
        /// 构造器
        /// </summary>
        public UnitOfWorkOfQuery()
        {
            this.QueryDbContext = DbContextBase.CommandInstance;
        }
        #endregion

        #region # 析构器

        /// <summary>
        /// 析构器
        /// </summary>
        ~UnitOfWorkOfQuery()
        {
            this.Dispose();
        }

        #endregion

        #region # 释放资源 —— void Dispose()
        /// <summary>
        /// 释放资源
        /// </summary>
        public void Dispose()
        {
            this.QueryDbContext?.Dispose();
        }
        #endregion

        #region # 实现

        //Single部分

        #region # 根据Id获取唯一实体对象 —— T SingleOrDefault(Guid id)
        /// <summary>
        /// 根据Id获取唯一实体对象，
        /// </summary>
        /// <param name="id">标识Id</param>
        /// <returns>实体对象</returns>
        /// <remarks>无该对象时返回null</remarks>
        public T SingleOrDefault<T>(Guid id) where T : PlainEntity
        {
            return this.SingleOrDefault<T>(x => x.Id == id);
        }
        #endregion

        #region # 根据Id获取唯一实体对象 —— T Single(Guid id)
        /// <summary>
        /// 根据Id获取唯一实体对象，
        /// </summary>
        /// <param name="id">标识Id</param>
        /// <returns>实体对象</returns>
        public T Single<T>(Guid id) where T : PlainEntity
        {
            T current = this.SingleOrDefault<T>(id);

            #region # 非空验证

            if (current == null)
            {
                throw new NullReferenceException($"Id为\"{id}\"的{typeof(T).Name}实体不存在！");
            }

            #endregion

            return current;
        }
        #endregion

        #region # 根据编号获取唯一实体对象 —— T SingleOrDefault(string number)
        /// <summary>
        /// 根据编号获取唯一实体对象，
        /// </summary>
        /// <param name="number">编号</param>
        /// <returns>实体对象</returns>
        /// <remarks>无该对象时返回null</remarks>
        public T SingleOrDefault<T>(string number) where T : RootEntity
        {
            #region # 验证

            if (string.IsNullOrWhiteSpace(number))
            {
                throw new ArgumentNullException(nameof(number), $"{typeof(T).Name}的编号不可为空！");
            }

            #endregion

            return this.SingleOrDefault<T>(x => x.Number == number);
        }
        #endregion

        #region # 根据编号获取唯一实体对象 —— T Single(string number)
        /// <summary>
        /// 根据编号获取唯一实体对象，
        /// </summary>
        /// <param name="number">编号</param>
        /// <returns>实体对象</returns>
        public T Single<T>(string number) where T : RootEntity
        {
            T current = this.SingleOrDefault<T>(number);

            #region # 非空验证

            if (current == null)
            {
                throw new NullReferenceException($"编号为\"{number}\"的{typeof(T).Name}实体不存在！");
            }

            #endregion

            return current;
        }
        #endregion

        #region # 获取默认或第一个实体对象 —— T FirstOrDefault()
        /// <summary>
        /// 获取默认或第一个实体对象，
        /// </summary>
        /// <remarks>无该对象时返回null</remarks>
        public virtual T FirstOrDefault<T>() where T : PlainEntity
        {
            return this.FindAllBySort<T>().FirstOrDefault();
        }
        #endregion

        #region # 根据条件获取唯一实体对象 —— T SingleOrDefault(Expression<Func<T, bool>> predicate)
        /// <summary>
        /// 根据条件获取唯一实体对象，
        /// </summary>
        /// <param name="predicate">条件</param>
        /// <returns>实体对象</returns>
        /// <remarks>无该对象时返回null</remarks>
        public T SingleOrDefault<T>(Expression<Func<T, bool>> predicate) where T : PlainEntity
        {
            #region # 验证

            if (predicate == null)
            {
                throw new ArgumentNullException(nameof(predicate), @"条件表达式不可为空！");
            }

            #endregion

            return this.FindAllInner<T>().SingleOrDefault(predicate);
        }
        #endregion

        #region # 根据条件获取第一个实体对象 —— T FirstOrDefault(Expression<Func<T, bool>> predicate)
        /// <summary>
        /// 根据条件获取第一个实体对象，
        /// </summary>
        /// <param name="predicate">条件</param>
        /// <returns>实体对象</returns>
        /// <remarks>无该对象时返回null</remarks>
        public T FirstOrDefault<T>(Expression<Func<T, bool>> predicate) where T : PlainEntity
        {
            #region # 验证

            if (predicate == null)
            {
                throw new ArgumentNullException(nameof(predicate), @"条件表达式不可为空！");
            }

            #endregion
            return this.Find(predicate).FirstOrDefault();
        }
        #endregion

        //ICollection部分

        #region # 获取实体对象列表 —— ICollection<T> FindAll()
        /// <summary>
        /// 获取实体对象列表
        /// </summary>
        /// <returns>实体对象列表</returns>
        public ICollection<T> FindAll<T>() where T : PlainEntity
        {
            return this.FindAllInner<T>().ToList();
        }
        #endregion

        #region # 根据关键字获取实体对象列表 —— ICollection<T> Find(string keywords)
        /// <summary>
        /// 根据关键字获取实体对象列表
        /// </summary>
        /// <returns>实体对象列表</returns>
        public ICollection<T> Find<T>(string keywords) where T : RootEntity
        {
            IQueryable<T> entities = this.FindAllInner<T>();
            if (!string.IsNullOrWhiteSpace(keywords))
            {
                entities = this.Find<T>(x => x.Keywords.Contains(keywords));
            }

            return entities.ToList();
        }
        #endregion

        #region # 根据条件获取实体对象列表 —— IQueryable<T> Find(Expression<Func<T, bool>> predicate)
        /// <summary>
        /// 根据条件获取实体对象列表
        /// </summary>
        /// <param name="predicate">条件表达式</param>
        /// <returns>实体对象列表</returns>
        public IQueryable<T> Find<T>(Expression<Func<T, bool>> predicate) where T : PlainEntity
        {
            #region # 验证

            if (predicate == null)
            {
                throw new ArgumentNullException(nameof(predicate), @"条件表达式不可为空！");
            }

            #endregion
            return this.FindAllInner<T>().Where(predicate);
        }
        #endregion

        #region # 根据条件获取实体对象列表（默认排序） —— virtual IOrderedQueryable<T> FindBySort(...
        /// <summary>
        /// 根据条件获取实体对象列表（默认排序）
        /// </summary>
        /// <returns>实体对象列表</returns>
        public IOrderedQueryable<T> FindBySort<T>(Expression<Func<T, bool>> condition) where T : PlainEntity
        {
            return this.Find(condition).OrderByDescending(x => x.AddedTime);
        }
        #endregion

        #region # 根据条件获取实体对象Id列表 —— IQueryable<Guid> FindIds(Expression<Func<T, bool>> predicate)
        /// <summary>
        /// 根据条件获取实体对象Id列表
        /// </summary>
        /// <param name="predicate">条件表达式</param>
        /// <returns>实体对象Id列表</returns>
        public IQueryable<Guid> FindIds<T>(Expression<Func<T, bool>> predicate) where T : PlainEntity
        {
            return this.Find(predicate).Select(x => x.Id);
        }
        #endregion

        #region # 根据条件分页获取实体对象列表 —— IQueryable<T> FindByPage(...
        /// <summary>
        /// 根据条件分页获取实体对象列表
        /// </summary>
        /// <param name="predicate">条件表达式</param>
        /// <param name="pageIndex">页码</param>
        /// <param name="pageSize">页容量</param>
        /// <param name="rowCount">总记录条数</param>
        /// <param name="pageCount">总页数</param>
        /// <returns>实体对象列表</returns>
        public IQueryable<T> FindByPage<T>(Expression<Func<T, bool>> predicate, int pageIndex, int pageSize, out int rowCount, out int pageCount) where T : PlainEntity
        {
            return this.FindBySort(predicate).ToPage(pageIndex, pageSize, out rowCount, out pageCount);
        }
        #endregion

        #region # 根据关键字分页获取实体对象列表 —— ICollection<T> FindByPage(...
        /// <summary>
        /// 根据关键字分页获取实体对象列表
        /// </summary>
        /// <param name="keywords">关键字</param>
        /// <param name="pageIndex">页码</param>
        /// <param name="pageSize">页容量</param>
        /// <param name="rowCount">总记录条数</param>
        /// <param name="pageCount">总页数</param>
        /// <returns>实体对象列表</returns>
        public ICollection<T> FindByPage<T>(string keywords, int pageIndex, int pageSize, out int rowCount, out int pageCount) where T : RootEntity
        {
            Expression<Func<T, bool>> condition;
            if (!string.IsNullOrWhiteSpace(keywords))
            {
                condition = x => x.Keywords.Contains(keywords);
            }
            else
            {
                condition = x => true;
            }

            return this.FindByPage<T>(condition, pageIndex, pageSize, out rowCount, out pageCount).ToList();
        }
        #endregion


        //IDictionary部分

        #region # 根据Id集获取实体对象字典 —— IDictionary<Guid, T> Find(IEnumerable<Guid> ids)
        /// <summary>
        /// 根据Id集获取实体对象字典
        /// </summary>
        /// <returns>实体对象字典</returns>
        /// <remarks>IDictionary[Guid, T]，[Id, 实体对象]</remarks>
        public IDictionary<Guid, T> Find<T>(IEnumerable<Guid> ids) where T : PlainEntity
        {
            #region # 验证

            Guid[] ids_ = ids?.Distinct().ToArray() ?? new Guid[0];
            if (!ids_.Any())
            {
                return new Dictionary<Guid, T>();
            }

            #endregion

            var entities = from entity in this.QueryDbContext.Set<T>()
                           where ids_.Contains(entity.Id)
                           select new { entity.Id, entity };

            return entities.ToDictionary(x => x.Id, x => x.entity);
        }
        #endregion

        #region # 根据编号集获取实体对象字典 —— IDictionary<string, T> Find(IEnumerable<string> numbers)
        /// <summary>
        /// 根据编号集获取实体对象字典
        /// </summary>
        /// <returns>实体对象字典</returns>
        /// <remarks>IDictionary[string, T]，[编号, 实体对象]</remarks>
        public IDictionary<string, T> Find<T>(IEnumerable<string> numbers) where T : RootEntity
        {
            #region # 验证

            string[] numbers_ = numbers?.Distinct().ToArray() ?? new string[0];
            if (!numbers_.Any())
            {
                return new Dictionary<string, T>();
            }

            #endregion

            var entities = from entity in this.QueryDbContext.Set<T>()
                where numbers_.Contains(entity.Number)
                select new { entity.Number, entity };

            return entities.ToDictionary(x => x.Number, x => x.entity);
        }
        #endregion

        //Count部分

        #region # 获取总记录条数 —— int Count()
        /// <summary>
        /// 获取总记录条数
        /// </summary>
        /// <returns>总记录条数</returns>
        public int Count<T>() where T : PlainEntity
        {
            return this.Count<T>(x => true);
        }
        #endregion

        #region # 根据条件获取记录条数 —— int Count(Expression<Func<T, bool>> predicate)
        /// <summary>
        /// 根据条件获取记录条数
        /// </summary>
        /// <param name="predicate">条件表达式</param>
        /// <returns>符合条件的记录条数</returns>
        public int Count<T>(Expression<Func<T, bool>> predicate) where T : PlainEntity
        {
            #region # 验证

            if (predicate == null)
            {
                throw new ArgumentNullException(nameof(predicate), @"条件表达式不可为空！");
            }

            #endregion

            return this.FindAllInner<T>().Count(predicate);
        }
        #endregion

        //Exists部分

        #region # 是否存在给定条件的实体对象 —— bool Exists(Expression<Func<T, bool>> predicate)

        /// <summary>
        /// 是否存在给定条件的实体对象
        /// </summary>
        /// <param name="predicate">条件</param>
        /// <returns>是否存在</returns>
        public bool Exists<T>(Expression<Func<T, bool>> predicate) where T : PlainEntity
        {
            #region # 验证

            if (predicate == null)
            {
                throw new ArgumentNullException(nameof(predicate), @"条件表达式不可为空！");
            }

            #endregion

            lock (Sync)
            {
                return this.FindAllInner<T>().Any(predicate);
            }
        }
        #endregion

        #region # 是否存在给定Id的实体对象 —— bool Exists(Guid id)
        /// <summary>
        /// 是否存在给定Id的实体对象
        /// </summary>
        /// <param name="id">标识Id</param>
        /// <returns>是否存在</returns>
        public bool Exists<T>(Guid id) where T : PlainEntity
        {
            return this.Exists<T>(x => x.Id == id);
        }
        #endregion

        #region # 是否存在给定编号的实体对象 —— bool ExistsNo(string number)
        /// <summary>
        /// 是否存在给定编号的实体对象
        /// </summary>
        /// <param name="number">编号</param>
        /// <returns>是否存在</returns>
        public bool ExistsNo<T>(string number) where T : RootEntity
        {
            #region # 验证

            if (string.IsNullOrWhiteSpace(number))
            {
                throw new ArgumentNullException(nameof(number), $"{typeof(T).Name}的编号不可为空！");
            }

            #endregion

            return this.Exists<T>(x => x.Number == number);
        }
        #endregion

        #region # 是否存在给定编号的实体对象 —— bool ExistsNo(Guid? id, string number)
        /// <summary>
        /// 是否存在给定编号的实体对象
        /// </summary>
        /// <param name="id">标识id</param>
        /// <param name="number">编号</param>
        /// <returns>是否存在</returns>
        public bool ExistsNo<T>(Guid? id, string number) where T : RootEntity
        {
            if (id != null)
            {
                T current = this.SingleOrDefault<T>(id.Value);

                if (current != null && current.Number == number)
                {
                    return false;
                }

                return this.ExistsNo<T>(number);
            }

            return this.ExistsNo<T>(number);
        }
        #endregion

        #region # 是否存在给定名称的实体对象 —— bool ExistsName(string name)
        /// <summary>
        /// 是否存在给定名称的实体对象
        /// </summary>
        /// <param name="name">名称</param>
        /// <returns>是否存在</returns>
        public bool ExistsName<T>(string name) where T : RootEntity
        {
            #region # 验证

            if (string.IsNullOrWhiteSpace(name))
            {
                throw new ArgumentNullException(nameof(name), $"{typeof(T).Name}的名称不可为空！");
            }

            #endregion

            return this.Exists<T>(x => x.Name == name);
        }
        #endregion

        #region # 是否存在给定名称的实体对象 —— bool ExistsName(Guid? id, string name)
        /// <summary>
        /// 是否存在给定名称的实体对象
        /// </summary>
        /// <param name="id">标识id</param>
        /// <param name="name">名称</param>
        /// <returns>是否存在</returns>
        public bool ExistsName<T>(Guid? id, string name) where T : RootEntity
        {
            if (id != null)
            {
                T current = this.SingleOrDefault<T>(id.Value);

                if (current != null && current.Name == name)
                {
                    return false;
                }

                return this.ExistsName<T>(name);
            }

            return this.ExistsName<T>(name);
        }
        #endregion

        #region # 是否存在给定名称的实体对象 —— bool ExistsName(string number, string name)
        /// <summary>
        /// 是否存在给定名称的实体对象
        /// </summary>
        /// <param name="number">编号</param>
        /// <param name="name">名称</param>
        /// <returns>是否存在</returns>
        public bool ExistsName<T>(string number, string name) where T : RootEntity
        {
            if (!string.IsNullOrWhiteSpace(number))
            {
                T current = this.SingleOrDefault<T>(number);

                if (current != null && current.Name == name)
                {
                    return false;
                }

                return this.ExistsName<T>(name);
            }

            return this.ExistsName<T>(name);
        }
        #endregion

        //其他

        #region # 执行SQL查询 —— ICollection<TEntity> ExecuteSqlQuery<TEntity>(string sql...
        /// <summary>
        /// 执行SQL查询
        /// </summary>
        /// <param name="sql">SQL语句</param>
        /// <param name="parameters">参数集</param>
        /// <returns>实体对象列表</returns>
        public ICollection<TEntity> ExecuteSqlQuery<TEntity>(string sql, params object[] parameters)
        {
            #region # 验证

            if (string.IsNullOrWhiteSpace(sql))
            {
                throw new ArgumentNullException(nameof(sql), @"SQL语句不可为空！");
            }

            #endregion

            return this.QueryDbContext.Database.SqlQuery<TEntity>(sql, parameters).ToList();
        }
        #endregion

        #region # Protected
        
        //IQueryable部分

        #region # 获取实体对象列表 —— virtual IQueryable<T> FindAllInner()
        /// <summary>
        /// 获取实体对象列表
        /// </summary>
        /// <returns>实体对象列表</returns>
        protected virtual IQueryable<T> FindAllInner<T>() where T : PlainEntity
        {
            Expression<Func<T, bool>> condition = RepositoryExtension.BuildFilterExpression<T>();
            return this.QueryDbContext.Set<T>().Where(condition);
        }
        #endregion

        #region # 获取实体对象列表（默认排序） —— IOrderedQueryable<T> FindAllBySort()
        /// <summary>
        /// 获取实体对象列表（默认排序）
        /// </summary>
        /// <returns>实体对象列表</returns>
        protected IOrderedQueryable<T> FindAllBySort<T>() where T : PlainEntity
        {
            return this.FindBySort<T>(x => true);
        }
        #endregion
        #endregion
        #endregion
    }
}
